Skip to content

feat(web-ui): /sessions/[id] detail page — SplitPane + AgentChatPanel + AgentTerminal (#509)#520

Merged
frankbria merged 3 commits into
mainfrom
feature/issue-509-sessions-id-detail-page
Apr 2, 2026
Merged

feat(web-ui): /sessions/[id] detail page — SplitPane + AgentChatPanel + AgentTerminal (#509)#520
frankbria merged 3 commits into
mainfrom
feature/issue-509-sessions-id-detail-page

Conversation

@frankbria

@frankbria frankbria commented Apr 2, 2026

Copy link
Copy Markdown
Owner

Summary

Implements #509 — the final integration page that assembles all session UI components into a working interactive session view.

  • New web-ui/src/app/sessions/[id]/page.tsx replacing the stub with full implementation
  • Header: back link, short session ID, state badge, cost display, End Session button with error feedback
  • Active sessions: SplitPane with AgentChatPanel (45%, left) + AgentTerminal (55%, right), per-session storageKey
  • Ended sessions: read-only AgentChatPanel with message history (REST fetch), no terminal, ended banner
  • Loading skeleton and error states (session not found + generic)
  • AgentChatPanel: added readOnly and initialMessages props for ended session view
  • sessionsApi: added getOne(id) and getMessages(id) to API client

Acceptance Criteria

  • Page loads session metadata and renders correct state (active vs ended)
  • AgentChatPanel is mounted and chat is functional for active sessions
  • AgentTerminal is mounted and terminal is functional for active sessions
  • SplitPane divider is draggable; position persists per session
  • [End Session] ends the session and redirects to list
  • Ended sessions show read-only message history, no input bar, no terminal
  • "Session not found" error state displayed correctly
  • Page title includes session short ID
  • Navigating back to /sessions shows updated session state

Test Plan

  • 19 unit tests written (TDD — tests written before implementation)
  • All 19 new tests passing
  • Full test suite: no new failures introduced (pre-existing failures unchanged)
  • ESLint: no new errors or warnings
  • Code review: addressed silent failure on end-session, SWR polling optimization for ended sessions, cost_usd null safety

Implementation Notes

  • Live cost counter: AgentChatPanel owns cost state via useAgentChat internally; header shows REST metadata cost
  • Ended session messages: fetched via GET /api/v2/sessions/{id}/messages (REST) on mount, passed as initialMessages to AgentChatPanel
  • SWR polling: refreshInterval is a function — polls every 5s for active sessions, stops for ended sessions
  • readOnly prop: when true, passes null to useAgentChat (no WS connection) and hides input bar

Closes #509

Summary by CodeRabbit

  • New Features

    • Full session detail page with short-ID title, state badge, cost, loading skeleton, and distinct “not found” vs generic error UI
    • Active sessions: split view with live chat and terminal, enabled “End Session” action with error handling and redirect on success
    • Ended sessions: ended banner, read-only chat populated from history, and disabled end action
  • Tests

    • Comprehensive tests for rendering, lifecycle states, actions, error paths, and metadata title

- New page wiring SplitPane + AgentChatPanel + AgentTerminal
- Header: back link, short session ID, state badge, cost, End Session button
- Active sessions: SplitPane with chat (45%) + terminal (55%), per-session storageKey
- Ended sessions: read-only AgentChatPanel with history, no terminal, ended banner
- Loading skeleton and error states (404 session-not-found + generic)
- Error feedback when End Session fails
- AgentChatPanel: readOnly + initialMessages props for ended session view
- sessionsApi: getOne() and getMessages() endpoints
- 19 unit tests covering all acceptance criteria
@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Convert the sessions detail route to a server page that renders a new client component SessionDetailClient; add session metadata and messages APIs, update AgentChatPanel to support read-only and initial messages, and add tests covering active/ended/error and end-session flows. (50 words)

Changes

Cohort / File(s) Summary
Route / Server page
web-ui/src/app/sessions/[id]/page.tsx
Converted page to an async server page accepting params, added/exported generateMetadata, and render SessionDetailClient with sessionId.
Client session UI
web-ui/src/app/sessions/[id]/SessionDetailClient.tsx
New client component that SWR-fetches session metadata, polls while active, loads messages for ended sessions, mounts SplitPane/AgentChatPanel/AgentTerminal conditionally, and handles "End Session" with API calls and navigation.
Chat component update
web-ui/src/components/sessions/AgentChatPanel.tsx
Added props readOnly?: boolean and initialMessages?: ChatMessage[]; when readOnly uses useAgentChat(null), sources messages from initialMessages if provided, and hides input controls for read-only mode.
API surface
web-ui/src/lib/api.ts
Extended sessionsApi with getOne(id: string) and getMessages(id: string, params?) (maps server message fields to client types).
Tests
web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx
New Jest/RTL suite mocking router, SWR, sessionsApi, and child components; verifies loading, active vs ended render paths, storageKey, read-only behavior, end-session success/error flows, and metadata generation.

Sequence Diagram

sequenceDiagram
    participant User
    participant ServerPage as Server: /sessions/[id]/page
    participant Client as SessionDetailClient
    participant API as sessionsApi
    participant Chat as AgentChatPanel
    participant Term as AgentTerminal
    participant Split as SplitPane
    participant Router as Router

    User->>ServerPage: Request /sessions/{id}
    ServerPage->>ServerPage: generateMetadata(params) -> title
    ServerPage-->>User: Render page with SessionDetailClient(sessionId)

    User->>Client: Client mounts with sessionId
    Client->>API: GET /api/v2/sessions/{id}
    API-->>Client: session metadata {id, state, ...}

    alt state == active
        Client->>Split: render SplitPane(storageKey=session-split-{id})
        Client->>Chat: mount AgentChatPanel(sessionId, readOnly=false)
        Client->>Term: mount AgentTerminal(sessionId)
        loop poll while active
            Client->>API: GET /api/v2/sessions/{id} (poll)
            API-->>Client: updated metadata
        end
        User->>Client: Click "End Session"
        Client->>API: DELETE /api/v2/sessions/{id}
        API-->>Client: success
        Client->>Router: push('/sessions')
    else state == ended
        Client->>API: GET /api/v2/sessions/{id}/messages
        API-->>Client: ChatMessage[] history
        Client->>Chat: render AgentChatPanel(readOnly=true, initialMessages)
        Client-->>Term: do not mount AgentTerminal
    end

    alt fetch error (404)
        API-->>Client: 404 error
        Client-->>User: "Session not found" + back link
    else other error
        API-->>Client: error
        Client-->>User: "Failed to load session"
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐰 I hopped in to see the session bloom,
Split panes settle, chat fills the room,
Ended stories soft and read-only trails,
Active sockets hum with lively tales,
A rabbit cheers — the session set to zoom! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title concisely and clearly summarizes the main change—implementing the /sessions/[id] detail page by integrating SplitPane, AgentChatPanel, and AgentTerminal into a working session view.
Linked Issues check ✅ Passed The pull request fully implements all coding requirements from issue #509, including the header with session metadata and End button, SplitPane layout with per-session storage, active/ended session differentiation, message history fetching, loading skeleton, error states, and metadata generation with session short ID.
Out of Scope Changes check ✅ Passed All changes are directly aligned with building the session detail page: SessionDetailClient component, server-side page wrapper with metadata, API helpers, AgentChatPanel enhancements for read-only mode, and comprehensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/issue-509-sessions-id-detail-page

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

Review: feat(web-ui): /sessions/[id] detail page (#509)

This is a well-structured implementation. The TDD approach, SWR polling strategy, and active/ended session branching are all solid. A few items worth addressing:


Bug: 'use client' directive in test file

web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx, line 1

'use client';  // ← should not be here

This is a Next.js component directive; it has no meaning in a Jest/Node.js test environment. It won't cause a test failure today but it's misleading and could cause confusion if the test runner ever changes. Remove it.


Layout: Hardcoded 48px fallback may break ended-session view

web-ui/src/app/sessions/[id]/page.tsx

<div className="flex-1 overflow-hidden" style={{ height: 'calc(100vh - var(--header-height, 48px))' }}>

Two issues here:

  1. --header-height doesn't appear to be set anywhere — so the 48px fallback is always used.
  2. When the session is ended, the banner (This session has ended.) adds another row below the header, but the height calc doesn't account for it. The body div will overflow by the banner's height.

Since the container is already flex-1 inside a flex column, the inline style is redundant — flex layout handles this already. Consider removing the style attribute entirely and relying on the existing flex-1 overflow-hidden classes, or set the CSS variable properly.


Minor: historyMessages as useEffect dependency is a code smell

web-ui/src/app/sessions/[id]/page.tsx

useEffect(() => {
  if (session?.state === 'ended' && historyMessages === undefined) {
    sessionsApi.getMessages(session.id).then(setHistoryMessages).catch(() => setHistoryMessages([]));
  }
}, [session, historyMessages]);  // ← historyMessages in deps

This works correctly (the === undefined guard prevents double-fetching), but ESLint's exhaustive-deps rule likely flags it, and it's unusual. A useRef flag is cleaner:

const messagesFetchedRef = useRef(false);
useEffect(() => {
  if (session?.state === 'ended' && !messagesFetchedRef.current) {
    messagesFetchedRef.current = true;
    sessionsApi.getMessages(session.id).then(setHistoryMessages).catch(() => setHistoryMessages([]));
  }
}, [session]);

Style: Condensed conditional wrapper in AgentChatPanel

web-ui/src/components/sessions/AgentChatPanel.tsx

{!readOnly && <div className="border-t bg-background px-4 py-3">
  {/* ...input bar content... */}
</div>}

Surrounding code wraps blocks consistently. This reads better as:

{!readOnly && (
  <div className="border-t bg-background px-4 py-3">
    {/* ...input bar content... */}
  </div>
)}

Accessibility: Error messages lack role="alert"

The inline end-session error and the generic error state are visually clear, but screen readers won't announce them unless they have role="alert". For the inline error in the header:

{endError && (
  <span className="text-xs text-destructive" role="alert">{endError}</span>
)}

Positive observations

  • SWR refreshInterval as a function (data) => data?.state === 'active' ? 5000 : 0 is exactly the right pattern — polling stops automatically for ended sessions.
  • handleEndSession correctly guards against double-submission with endingSession state.
  • id URL encoding via encodeURIComponent in getOne and getMessages is correct.
  • cost_usd ?? 0 null safety is clean.
  • The 19-test suite covers loading, active, ended, error, and button-state scenarios comprehensively.

The 'use client' in the test file and the CSS height calc issue are the ones I'd prioritize fixing before merge.

- Remove 'use client' directive from test file (no-op in Jest)
- Remove redundant inline height style (flex-1 handles this)
- Use useRef flag for message history fetch (avoids dep array smell)
- Fix conditional JSX wrapper formatting in AgentChatPanel
- Add role='alert' to error message for a11y
@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

Follow-up Review

All five issues flagged in the previous review have been addressed:

  • 'use client' in test file — not present; test file is clean.
  • Height calc inline style — removed; body uses flex-1 overflow-hidden only, letting flex layout handle the dimensions correctly.
  • useRef for message fetch guardmessagesFetchedRef pattern is in place; no double-fetch risk and no ESLint dep-array noise.
  • Parenthesized conditional in AgentChatPanel{!readOnly && ( is consistent with surrounding code style.
  • role="alert" on end-session error — present on the inline error span.

No new concerns. The SWR polling function, null-safe cost display, and encodeURIComponent in API calls are all solid. 19 tests covering loading/active/ended/error/button states is comprehensive coverage.

Approving — ready to merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/src/app/sessions/`[id]/page.tsx:
- Around line 73-80: The current useEffect swallowing fetch failures via
catch(() => setHistoryMessages([])) makes network/500 errors indistinguishable
from a genuinely empty transcript; update the load logic used with
useEffect/session?.state/historyMessages to track loading and error separately
(e.g., add historyLoading and historyError state), call
sessionsApi.getMessages(session.id).then(setHistoryMessages).catch(err =>
setHistoryError(err)) instead of setting an empty array on error, and ensure the
read-only rendering branch checks historyError to show an explicit failure state
rather than treating errors as an empty chat.
- Around line 83-95: The handleEndSession handler currently calls
sessionsApi.end immediately; add a user confirmation step (e.g., using
window.confirm or your app’s confirmation modal) before making the DELETE call
so a stray click won’t terminate the session. Specifically, inside
handleEndSession (before setEndingSession and sessionsApi.end(session.id))
prompt the user and return early if they cancel; keep existing error handling
and state updates (setEndError, setEndingSession, router.push) intact. Ensure
the check still respects session and session.state !== 'active' and include the
session id or a clear message in the confirmation prompt for clarity.
- Around line 56-60: The code currently treats any non-active session (checks
like !isActive) as ended which mislabels 'paused' sessions and removes live UI;
update the logic so polling and UI branches explicitly check session.state ===
'ended' where you currently stop polling or render the "ended" UI (notably in
the useSWR refreshInterval predicate and the places that render the ended
banner, hide the terminal, set chat to read-only, or disable the end button).
Concretely, change the refreshInterval handler in the useSWR call to only stop
polling when session.state === 'ended', replace other !isActive conditional
branches with explicit session.state === 'ended' checks, and add separate
handling for session.state === 'paused' to keep or present the appropriate
paused UI (e.g., a paused banner and preserving live terminal/chat behavior) in
the same components referenced around the useSWR call and the conditional blocks
that render the ended banner, terminal, chat read-only state, and end button.

In `@web-ui/src/components/sessions/AgentChatPanel.tsx`:
- Around line 199-209: AgentChatPanel is using live hook state from useAgentChat
even when readOnly is true, causing the header and empty-state to show
active-session chrome; modify the rendering logic to derive display values from
readOnly rather than the hook state: call useAgentChat(readOnly ? null :
sessionId) as-is but compute local vars like displayStatus, displayCost, and
emptyStateCopy (e.g., displayStatus = readOnly ? 'ended' or 'disconnected' and
displayCost = readOnly ? undefined or the stored session cost; emptyStateCopy
should come from initialMessages presence when readOnly) and use those vars in
the header and empty-state rendering so ended/read-only sessions don't show
connected/socket UI or interactive prompts. Ensure references include
AgentChatPanel, useAgentChat, state, status, costUsd, initialMessages, and
readOnly.

In `@web-ui/src/lib/api.ts`:
- Around line 674-687: The API mapper in the api.get(...) call currently narrows
the response to an inline DTO and drops per-message metadata (the mapping that
returns {id, role, content, createdAt}), so add a proper Message DTO in
web-ui/src/types/index.ts that includes all fields returned by /messages (e.g.,
id, role, content, created_at plus toolName, toolInput, metadata or any other
per-message fields), import that type into web-ui/src/lib/api.ts, update the
api.get<> generic to use the new DTO, and change the response.data.map(...)
normalization to preserve and normalize the full payload (mapping created_at ->
createdAt and passing through toolName/toolInput/metadata) so AgentChatPanel
receives the tool-call details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c799008e-20d1-4b7a-9469-2af72c057459

📥 Commits

Reviewing files that changed from the base of the PR and between b2dff53 and 87e4c2d.

📒 Files selected for processing (4)
  • web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx
  • web-ui/src/app/sessions/[id]/page.tsx
  • web-ui/src/components/sessions/AgentChatPanel.tsx
  • web-ui/src/lib/api.ts

Comment thread web-ui/src/app/sessions/[id]/page.tsx Outdated
Comment on lines +56 to +60
const { data: session, isLoading, error } = useSWR<Session>(
sessionId ? `/api/v2/sessions/${sessionId}` : null,
() => sessionsApi.getOne(sessionId),
{ refreshInterval: (data) => (data?.state === 'active' ? 5000 : 0) }
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle paused separately from ended.

SessionState also includes 'paused', but every !isActive path here assumes the session is ended: polling stops, the ended banner renders, the terminal disappears, chat becomes read-only, and the end button is disabled. A paused session will therefore be mislabeled and lose its live UI. Branch on session.state === 'ended' explicitly, then define the paused behavior separately.

Also applies to: 145-145, 186-213

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 56 - 60, The code
currently treats any non-active session (checks like !isActive) as ended which
mislabels 'paused' sessions and removes live UI; update the logic so polling and
UI branches explicitly check session.state === 'ended' where you currently stop
polling or render the "ended" UI (notably in the useSWR refreshInterval
predicate and the places that render the ended banner, hide the terminal, set
chat to read-only, or disable the end button). Concretely, change the
refreshInterval handler in the useSWR call to only stop polling when
session.state === 'ended', replace other !isActive conditional branches with
explicit session.state === 'ended' checks, and add separate handling for
session.state === 'paused' to keep or present the appropriate paused UI (e.g., a
paused banner and preserving live terminal/chat behavior) in the same components
referenced around the useSWR call and the conditional blocks that render the
ended banner, terminal, chat read-only state, and end button.

Comment thread web-ui/src/app/sessions/[id]/page.tsx Outdated
Comment on lines +73 to +80
// Load message history for ended sessions via REST
useEffect(() => {
if (session?.state === 'ended' && historyMessages === undefined) {
sessionsApi
.getMessages(session.id)
.then(setHistoryMessages)
.catch(() => setHistoryMessages([]));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t mask history-fetch failures as an empty transcript.

catch(() => setHistoryMessages([])) makes a network/500 failure indistinguishable from a session that genuinely has no messages. The ended-session path then renders a misleading empty chat instead of an explicit failure state. Track history loading/error separately and surface that in the read-only branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 73 - 80, The current
useEffect swallowing fetch failures via catch(() => setHistoryMessages([]))
makes network/500 errors indistinguishable from a genuinely empty transcript;
update the load logic used with useEffect/session?.state/historyMessages to
track loading and error separately (e.g., add historyLoading and historyError
state), call
sessionsApi.getMessages(session.id).then(setHistoryMessages).catch(err =>
setHistoryError(err)) instead of setting an empty array on error, and ensure the
read-only rendering branch checks historyError to show an explicit failure state
rather than treating errors as an empty chat.

Comment thread web-ui/src/app/sessions/[id]/page.tsx Outdated
Comment on lines +83 to +95
const handleEndSession = useCallback(async () => {
if (!session || session.state !== 'active') return;
setEndingSession(true);
setEndError(null);
try {
await sessionsApi.end(session.id);
router.push('/sessions');
} catch {
setEndError('Failed to end session. Please try again.');
} finally {
setEndingSession(false);
}
}, [session, router]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add the missing confirmation before sending DELETE.

This handler ends the session on the first click, but the PR objective calls for a confirmation step before terminating an active session. Please gate the request behind a confirm dialog so a stray click can’t end the session immediately.

Minimal fix
 const handleEndSession = useCallback(async () => {
   if (!session || session.state !== 'active') return;
+  if (!window.confirm(`End session #${shortId(session.id)}?`)) return;
   setEndingSession(true);
   setEndError(null);
   try {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleEndSession = useCallback(async () => {
if (!session || session.state !== 'active') return;
setEndingSession(true);
setEndError(null);
try {
await sessionsApi.end(session.id);
router.push('/sessions');
} catch {
setEndError('Failed to end session. Please try again.');
} finally {
setEndingSession(false);
}
}, [session, router]);
const handleEndSession = useCallback(async () => {
if (!session || session.state !== 'active') return;
if (!window.confirm(`End session #${shortId(session.id)}?`)) return;
setEndingSession(true);
setEndError(null);
try {
await sessionsApi.end(session.id);
router.push('/sessions');
} catch {
setEndError('Failed to end session. Please try again.');
} finally {
setEndingSession(false);
}
}, [session, router]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 83 - 95, The
handleEndSession handler currently calls sessionsApi.end immediately; add a user
confirmation step (e.g., using window.confirm or your app’s confirmation modal)
before making the DELETE call so a stray click won’t terminate the session.
Specifically, inside handleEndSession (before setEndingSession and
sessionsApi.end(session.id)) prompt the user and return early if they cancel;
keep existing error handling and state updates (setEndError, setEndingSession,
router.push) intact. Ensure the check still respects session and session.state
!== 'active' and include the session id or a clear message in the confirmation
prompt for clarity.

Comment on lines +199 to +209
export function AgentChatPanel({
sessionId,
className,
readOnly = false,
initialMessages,
}: AgentChatPanelProps) {
const { state, sendMessage, interrupt } = useAgentChat(
readOnly ? null : sessionId
);
const { status, costUsd } = state;
const messages = initialMessages ?? state.messages;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Read-only mode still renders live-session chrome.

When readOnly is true, useAgentChat(null) returns the hook’s initial state. That makes the panel show the default green/connected status and $0.0000, and if initialMessages is still missing it also falls back to the interactive empty-state copy. Please branch the header/empty state for read-only mode so ended sessions don’t advertise a live websocket or prompt the user to start a conversation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/sessions/AgentChatPanel.tsx` around lines 199 - 209,
AgentChatPanel is using live hook state from useAgentChat even when readOnly is
true, causing the header and empty-state to show active-session chrome; modify
the rendering logic to derive display values from readOnly rather than the hook
state: call useAgentChat(readOnly ? null : sessionId) as-is but compute local
vars like displayStatus, displayCost, and emptyStateCopy (e.g., displayStatus =
readOnly ? 'ended' or 'disconnected' and displayCost = readOnly ? undefined or
the stored session cost; emptyStateCopy should come from initialMessages
presence when readOnly) and use those vars in the header and empty-state
rendering so ended/read-only sessions don't show connected/socket UI or
interactive prompts. Ensure references include AgentChatPanel, useAgentChat,
state, status, costUsd, initialMessages, and readOnly.

Comment thread web-ui/src/lib/api.ts
Comment on lines +674 to +687
const response = await api.get<
Array<{
id: string;
role: string;
content: string;
created_at: string;
}>
>(`/api/v2/sessions/${encodeURIComponent(id)}/messages`, { params });
return response.data.map((m) => ({
id: m.id,
role: m.role as ChatMessage['role'],
content: m.content,
createdAt: m.created_at,
}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve message metadata in ended-session history.

This mapper narrows the REST payload down to { id, role, content, created_at }, but the /messages response also carries per-message metadata. AgentChatPanel uses toolName/toolInput for tool-call rows, so ended-session transcripts will lose those details here. Please move the response DTO into web-ui/src/types/index.ts and normalize the full payload instead of dropping metadata behind an inline anonymous type.

As per coding guidelines, "TypeScript types must be defined in web-ui/src/types/index.ts and API client functionality in web-ui/src/lib/api.ts".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/lib/api.ts` around lines 674 - 687, The API mapper in the
api.get(...) call currently narrows the response to an inline DTO and drops
per-message metadata (the mapping that returns {id, role, content, createdAt}),
so add a proper Message DTO in web-ui/src/types/index.ts that includes all
fields returned by /messages (e.g., id, role, content, created_at plus toolName,
toolInput, metadata or any other per-message fields), import that type into
web-ui/src/lib/api.ts, update the api.get<> generic to use the new DTO, and
change the response.data.map(...) normalization to preserve and normalize the
full payload (mapping created_at -> createdAt and passing through
toolName/toolInput/metadata) so AgentChatPanel receives the tool-call details.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
web-ui/src/app/sessions/[id]/page.tsx (3)

85-97: ⚠️ Potential issue | 🟠 Major

Missing confirmation before ending session.

The PR objectives state the End Session button should "confirm" before terminating. Currently, a single click immediately calls DELETE /api/v2/sessions/{id} with no safeguard.

Add a confirmation prompt
 const handleEndSession = useCallback(async () => {
   if (!session || session.state !== 'active') return;
+  if (!window.confirm(`End session #${shortId(session.id)}?`)) return;
   setEndingSession(true);
   setEndError(null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 85 - 97, The end-session
flow in handleEndSession calls sessionsApi.end(session.id) immediately with no
user confirmation; update handleEndSession to display a confirmation prompt
(e.g., window.confirm or a modal) before calling sessionsApi.end, and only
proceed to setEndingSession(true)/call sessionsApi.end/route to '/sessions' when
the user confirms; ensure cancellation leaves state unchanged and still clears
setEndingSession(false) in the finally block, and reference handleEndSession and
sessionsApi.end when making the change.

74-83: ⚠️ Potential issue | 🟠 Major

Network errors in message history fetch are silently swallowed.

The .catch(() => setHistoryMessages([])) makes a 500 error indistinguishable from a genuinely empty session. Users see an empty chat with no indication that the history failed to load.

Track loading/error states explicitly:

+ const [historyError, setHistoryError] = useState<string | null>(null);

  useEffect(() => {
    if (session?.state === 'ended' && !messagesFetchedRef.current) {
      messagesFetchedRef.current = true;
      sessionsApi
        .getMessages(session.id)
        .then(setHistoryMessages)
-       .catch(() => setHistoryMessages([]));
+       .catch((err) => {
+         setHistoryError(err?.detail ?? 'Failed to load message history');
+         setHistoryMessages([]);
+       });
    }
  }, [session]);

Then surface historyError in the read-only AgentChatPanel branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 74 - 83, The current
useEffect swallowing network errors (sessionsApi.getMessages(...).catch(() =>
setHistoryMessages([]))) hides 500s; update the logic to track loading and error
states by introducing a historyLoading/historyError state (or refs) and set
historyLoading true before calling sessionsApi.getMessages(session.id), set
historyLoading false and setHistoryMessages on success, and on catch set
historyError with the caught error (not just an empty array) and leave
historyMessages untouched or empty as appropriate; ensure messagesFetchedRef is
still used to avoid duplicate fetches and pass historyError (and optionally
historyLoading) down into the read-only AgentChatPanel branch so the UI can
surface a clear failure message instead of silently showing an empty chat.

56-60: ⚠️ Potential issue | 🟠 Major

paused sessions are incorrectly treated as ended.

The refreshInterval callback stops polling for any non-active state. Combined with isActive = session.state === 'active' at line 147, a paused session will:

  • Stop polling (may or may not be desired)
  • Display the "This session has ended" banner (incorrect)
  • Hide the terminal and show read-only chat (incorrect)

Consider handling paused explicitly:

-    { refreshInterval: (data) => (data?.state === 'active' ? 5000 : 0) }
+    { refreshInterval: (data) => (data?.state === 'active' || data?.state === 'paused' ? 5000 : 0) }

And at line 147+:

   const isActive = session.state === 'active';
+  const isEnded = session.state === 'ended';
   const sid = shortId(session.id);

Then use isEnded for the banner (line 189) and read-only logic (line 208).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 56 - 60, The SWR
refreshInterval currently treats any non-'active' session (including 'paused')
as stopped and the UI sets isActive = session.state === 'active', causing paused
sessions to be shown as ended; update the useSWR refreshInterval callback to
only stop polling when state === 'ended' (i.e., return 5000 for 'active' and
'paused', 0 for 'ended'), then introduce explicit helpers like isPaused =
session?.state === 'paused' and isEnded = session?.state === 'ended' (keep
isActive = session?.state === 'active') and use isEnded for the "session has
ended" banner and read-only/chat visibility logic instead of negating isActive.
🧹 Nitpick comments (1)
web-ui/src/app/sessions/[id]/page.tsx (1)

208-214: Consider showing a loading state while fetching message history.

When historyMessages is undefined (still loading), the AgentChatPanel falls back to an empty message list. Users briefly see an empty chat before history loads. A loading indicator or skeleton within the panel would improve perceived responsiveness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 208 - 214, The
AgentChatPanel currently receives initialMessages={historyMessages} and shows an
empty chat while historyMessages is undefined; update the rendering to detect
when historyMessages is undefined and show a loading indicator/skeleton instead
of immediately rendering AgentChatPanel, or extend AgentChatPanel to accept an
isLoading prop and render its internal skeleton when isLoading is true;
reference the historyMessages value and the AgentChatPanel component
(initialMessages prop) and ensure the loading state is toggled until the fetched
historyMessages becomes defined so users see a spinner/skeleton instead of a
blank chat.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/src/app/sessions/`[id]/page.tsx:
- Around line 107-108: The 404 detection in the error handling for the session
page is using the wrong ApiError property; replace the check error?.status ===
404 with error?.status_code === 404 (or check both if you want to be defensive)
so the isNotFound computation in page.tsx correctly detects "Session not found"
errors; update the isNotFound assignment near the top of the page component that
currently references error?.status and keep the existing error?.detail check
intact.

---

Duplicate comments:
In `@web-ui/src/app/sessions/`[id]/page.tsx:
- Around line 85-97: The end-session flow in handleEndSession calls
sessionsApi.end(session.id) immediately with no user confirmation; update
handleEndSession to display a confirmation prompt (e.g., window.confirm or a
modal) before calling sessionsApi.end, and only proceed to
setEndingSession(true)/call sessionsApi.end/route to '/sessions' when the user
confirms; ensure cancellation leaves state unchanged and still clears
setEndingSession(false) in the finally block, and reference handleEndSession and
sessionsApi.end when making the change.
- Around line 74-83: The current useEffect swallowing network errors
(sessionsApi.getMessages(...).catch(() => setHistoryMessages([]))) hides 500s;
update the logic to track loading and error states by introducing a
historyLoading/historyError state (or refs) and set historyLoading true before
calling sessionsApi.getMessages(session.id), set historyLoading false and
setHistoryMessages on success, and on catch set historyError with the caught
error (not just an empty array) and leave historyMessages untouched or empty as
appropriate; ensure messagesFetchedRef is still used to avoid duplicate fetches
and pass historyError (and optionally historyLoading) down into the read-only
AgentChatPanel branch so the UI can surface a clear failure message instead of
silently showing an empty chat.
- Around line 56-60: The SWR refreshInterval currently treats any non-'active'
session (including 'paused') as stopped and the UI sets isActive = session.state
=== 'active', causing paused sessions to be shown as ended; update the useSWR
refreshInterval callback to only stop polling when state === 'ended' (i.e.,
return 5000 for 'active' and 'paused', 0 for 'ended'), then introduce explicit
helpers like isPaused = session?.state === 'paused' and isEnded = session?.state
=== 'ended' (keep isActive = session?.state === 'active') and use isEnded for
the "session has ended" banner and read-only/chat visibility logic instead of
negating isActive.

---

Nitpick comments:
In `@web-ui/src/app/sessions/`[id]/page.tsx:
- Around line 208-214: The AgentChatPanel currently receives
initialMessages={historyMessages} and shows an empty chat while historyMessages
is undefined; update the rendering to detect when historyMessages is undefined
and show a loading indicator/skeleton instead of immediately rendering
AgentChatPanel, or extend AgentChatPanel to accept an isLoading prop and render
its internal skeleton when isLoading is true; reference the historyMessages
value and the AgentChatPanel component (initialMessages prop) and ensure the
loading state is toggled until the fetched historyMessages becomes defined so
users see a spinner/skeleton instead of a blank chat.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0ff2a45c-0dfd-4836-9635-19bfddda92cb

📥 Commits

Reviewing files that changed from the base of the PR and between 87e4c2d and cd8e6de.

📒 Files selected for processing (3)
  • web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx
  • web-ui/src/app/sessions/[id]/page.tsx
  • web-ui/src/components/sessions/AgentChatPanel.tsx
✅ Files skipped from review due to trivial changes (1)
  • web-ui/src/tests/components/sessions/SessionDetailPage.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-ui/src/components/sessions/AgentChatPanel.tsx

Comment thread web-ui/src/app/sessions/[id]/page.tsx Outdated
Comment on lines +107 to +108
if (error || !session) {
const isNotFound = error?.status === 404 || error?.detail === 'Session not found';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

404 detection uses wrong property name.

Per the API interceptor (see web-ui/src/lib/api.ts lines 99-110), the ApiError object has status_code, not status. The current check error?.status === 404 will always be undefined === 404false, so 404 errors will render as generic failures instead of "Session not found".

- const isNotFound = error?.status === 404 || error?.detail === 'Session not found';
+ const isNotFound = error?.status_code === 404 || error?.detail === 'Session not found';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (error || !session) {
const isNotFound = error?.status === 404 || error?.detail === 'Session not found';
if (error || !session) {
const isNotFound = error?.status_code === 404 || error?.detail === 'Session not found';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/page.tsx around lines 107 - 108, The 404
detection in the error handling for the session page is using the wrong ApiError
property; replace the check error?.status === 404 with error?.status_code ===
404 (or check both if you want to be defensive) so the isNotFound computation in
page.tsx correctly detects "Session not found" errors; update the isNotFound
assignment near the top of the page component that currently references
error?.status and keep the existing error?.detail check intact.

…ic title metadata

- page.tsx: server component exporting generateMetadata for dynamic page title
- SessionDetailClient.tsx: client component with all interactive logic
- Tests now import SessionDetailClient directly + test generateMetadata
- 20 tests passing
@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

Follow-up Review

All five issues from my previous review have been addressed:

  • 'use client' in test file — removed. Test file now starts cleanly with imports.
  • Hardcoded calc(100vh - 48px) height — removed. Body div is plain flex-1 overflow-hidden, relying correctly on flex layout.
  • historyMessages in useEffect deps — replaced with useRef flag (messagesFetchedRef). Cleaner and lint-clean.
  • Condensed conditional wrapper — input bar now uses proper parentheses formatting.
  • Missing role="alert" on error — added to the inline end-session error span.

A couple of small observations on the new structure (neither blocks merge):

Good split: Extracting the interactive logic into SessionDetailClient.tsx and keeping page.tsx as a thin async server component is the right call for Next.js App Router. generateMetadata with async params is correctly handled.

Minor — SWR key vs fetcher mismatch: The SWR key is the URL string /api/v2/sessions/${sessionId} but the fetcher ignores the key and calls sessionsApi.getOne(sessionId) via closure. This works fine (SWR only uses the key for caching/deduplication), but it's slightly unconventional. Not worth changing now.

Test coverage: The generateMetadata test via dynamic import is a nice touch — catches regressions in the metadata function without needing a full Next.js render.

Ready to merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (4)
web-ui/src/app/sessions/[id]/SessionDetailClient.tsx (4)

102-103: ⚠️ Potential issue | 🔴 Critical

Use status_code for the not-found branch.

The API error shape here is status_code, not status, so real 404s will fall through to the generic failure UI instead of “Session not found”. Please update this check and the matching test mocks together.

Suggested fix
-    const isNotFound = error?.status === 404 || error?.detail === 'Session not found';
+    const isNotFound = error?.status_code === 404 || error?.detail === 'Session not found';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/SessionDetailClient.tsx around lines 102 - 103,
The not-found check in SessionDetailClient.tsx uses error?.status but the API
returns error.status_code; update the condition in the isNotFound assignment to
use error?.status_code === 404 (and keep the existing detail check), and update
any unit/test mocks that assert or simulate error.status to use status_code
instead so 404 error flows hit the “Session not found” branch; look for the
isNotFound variable and related tests/mocks in this component and adjust them
accordingly.

70-77: ⚠️ Potential issue | 🟠 Major

Surface transcript-load failures instead of showing an empty chat.

catch(() => setHistoryMessages([])) makes a real /messages failure indistinguishable from “this session has no messages”. Because Line 72 flips the ref before the request, a transient failure also blocks any retry for the rest of the mount. Track loading/error separately and render that in the read-only branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/SessionDetailClient.tsx around lines 70 - 77,
The current useEffect flips messagesFetchedRef.current before calling
sessionsApi.getMessages and swallows failures by calling catch(() =>
setHistoryMessages([])), which makes errors indistinguishable from an empty
transcript and prevents retries; change the logic in the useEffect that
references messagesFetchedRef, sessionsApi.getMessages, and setHistoryMessages
to: delay setting messagesFetchedRef.current = true until after a successful
response, introduce separate state (e.g., historyLoading and historyError) to
track loading and error states, set historyLoading true before the request and
false after, set historyError on failure instead of replacing messages with an
empty array, and update the read-only render branch to show loading/error UI
based on historyLoading/historyError so real failures are surfaced and retries
are possible.

61-62: ⚠️ Potential issue | 🟠 Major

Don’t collapse every non-active state into the ended UX.

Polling, the disabled end button, the ended banner, and the read-only/chat-only layout all key off session.state === 'active'. That makes any other state look “ended” and strips the live UI prematurely; branch on session.state === 'ended' explicitly and handle the remaining states separately.

Also applies to: 142-142, 175-176, 184-210

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/SessionDetailClient.tsx around lines 61 - 62,
The code treats any non-'active' session as "ended"; change the logic to
explicitly check for session.state === 'ended' instead of negating 'active'.
Update the refreshInterval callback (where refreshInterval: (data) =>
(data?.state === 'active' ? 5000 : 0)), the End button disabled logic, the ended
banner render, and the read-only/chat-only layout branches in
SessionDetailClient (they currently use session.state === 'active'); make them
branch off session.state === 'ended' for the ended UX and handle other states
(e.g., 'paused', 'pending', 'initializing') separately so they keep live UI
where appropriate.

80-90: ⚠️ Potential issue | 🟠 Major

Confirm before sending the destructive DELETE.

This still terminates the session on the first click, but the PR objective requires a confirmation step. Please gate the request behind a confirm dialog/modal and return early on cancel.

Minimal fix
 const handleEndSession = useCallback(async () => {
   if (!session || session.state !== 'active') return;
+  if (!window.confirm(`End session #${shortId(session.id)}?`)) return;
   setEndingSession(true);
   setEndError(null);
   try {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/sessions/`[id]/SessionDetailClient.tsx around lines 80 - 90,
The handleEndSession function currently calls sessionsApi.end immediately; gate
this destructive action by prompting the user and returning early on cancel.
Update handleEndSession to show a confirmation dialog/modal (e.g., using
window.confirm or the existing modal component) before calling
sessionsApi.end(session.id); if the user cancels, do not call sessionsApi.end,
do not setEndingSession, and simply return. Keep existing error handling
(setEndError) and state updates (setEndingSession, router.push) unchanged for
the confirmed path.
🧹 Nitpick comments (1)
web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx (1)

24-39: Assert the ended-session transcript wiring, not just the read-only chrome.

The requirement here is “load /messages into AgentChatPanel”. Because the mock drops initialMessages and none of the ended-session tests wait for sessionsApi.getMessages, this suite would stay green if the transcript fetch stopped working.

Small coverage upgrade
 jest.mock('@/components/sessions/AgentChatPanel', () => ({
   AgentChatPanel: ({
     sessionId,
     readOnly,
+    initialMessages,
   }: {
     sessionId: string;
     readOnly?: boolean;
+    initialMessages?: unknown[];
   }) => (
     <div
       data-testid="agent-chat-panel"
       data-session-id={sessionId}
       data-read-only={readOnly ? 'true' : 'false'}
+      data-initial-count={initialMessages?.length ?? -1}
     >
       {!readOnly && <textarea aria-label="Message input" />}
     </div>
   ),
 }));
 render(<SessionDetailClient sessionId={SESSION_ID} />);
- expect(screen.getByTestId('agent-chat-panel')).toHaveAttribute('data-read-only', 'true');
+ await waitFor(() => expect(mockSessApiGetMessages).toHaveBeenCalledWith(SESSION_ID));
+ expect(screen.getByTestId('agent-chat-panel')).toHaveAttribute('data-read-only', 'true');
+ expect(screen.getByTestId('agent-chat-panel')).toHaveAttribute('data-initial-count', '0');

Also applies to: 226-266

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx` around
lines 24 - 39, The mock for AgentChatPanel in SessionDetailPage.test.tsx drops
the initialMessages prop so the tests never verify that sessionsApi.getMessages
data is wired into the panel; update the mock for AgentChatPanel to accept and
render an initialMessages prop (or at least expose it via a data attribute) and
then update the ended-session tests to wait for sessionsApi.getMessages to
resolve and assert that the returned messages appear in the mocked
AgentChatPanel (or that initialMessages equals the API response). Target the
AgentChatPanel mock and the tests referencing sessionsApi.getMessages so the
suite will fail if initialMessages/transcript wiring breaks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@web-ui/src/app/sessions/`[id]/SessionDetailClient.tsx:
- Around line 102-103: The not-found check in SessionDetailClient.tsx uses
error?.status but the API returns error.status_code; update the condition in the
isNotFound assignment to use error?.status_code === 404 (and keep the existing
detail check), and update any unit/test mocks that assert or simulate
error.status to use status_code instead so 404 error flows hit the “Session not
found” branch; look for the isNotFound variable and related tests/mocks in this
component and adjust them accordingly.
- Around line 70-77: The current useEffect flips messagesFetchedRef.current
before calling sessionsApi.getMessages and swallows failures by calling catch(()
=> setHistoryMessages([])), which makes errors indistinguishable from an empty
transcript and prevents retries; change the logic in the useEffect that
references messagesFetchedRef, sessionsApi.getMessages, and setHistoryMessages
to: delay setting messagesFetchedRef.current = true until after a successful
response, introduce separate state (e.g., historyLoading and historyError) to
track loading and error states, set historyLoading true before the request and
false after, set historyError on failure instead of replacing messages with an
empty array, and update the read-only render branch to show loading/error UI
based on historyLoading/historyError so real failures are surfaced and retries
are possible.
- Around line 61-62: The code treats any non-'active' session as "ended"; change
the logic to explicitly check for session.state === 'ended' instead of negating
'active'. Update the refreshInterval callback (where refreshInterval: (data) =>
(data?.state === 'active' ? 5000 : 0)), the End button disabled logic, the ended
banner render, and the read-only/chat-only layout branches in
SessionDetailClient (they currently use session.state === 'active'); make them
branch off session.state === 'ended' for the ended UX and handle other states
(e.g., 'paused', 'pending', 'initializing') separately so they keep live UI
where appropriate.
- Around line 80-90: The handleEndSession function currently calls
sessionsApi.end immediately; gate this destructive action by prompting the user
and returning early on cancel. Update handleEndSession to show a confirmation
dialog/modal (e.g., using window.confirm or the existing modal component) before
calling sessionsApi.end(session.id); if the user cancels, do not call
sessionsApi.end, do not setEndingSession, and simply return. Keep existing error
handling (setEndError) and state updates (setEndingSession, router.push)
unchanged for the confirmed path.

---

Nitpick comments:
In `@web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx`:
- Around line 24-39: The mock for AgentChatPanel in SessionDetailPage.test.tsx
drops the initialMessages prop so the tests never verify that
sessionsApi.getMessages data is wired into the panel; update the mock for
AgentChatPanel to accept and render an initialMessages prop (or at least expose
it via a data attribute) and then update the ended-session tests to wait for
sessionsApi.getMessages to resolve and assert that the returned messages appear
in the mocked AgentChatPanel (or that initialMessages equals the API response).
Target the AgentChatPanel mock and the tests referencing sessionsApi.getMessages
so the suite will fail if initialMessages/transcript wiring breaks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7300ec73-e1f8-4fed-8792-de9c979e563c

📥 Commits

Reviewing files that changed from the base of the PR and between cd8e6de and e6e9acc.

📒 Files selected for processing (3)
  • web-ui/src/__tests__/components/sessions/SessionDetailPage.test.tsx
  • web-ui/src/app/sessions/[id]/SessionDetailClient.tsx
  • web-ui/src/app/sessions/[id]/page.tsx

@frankbria frankbria merged commit a4bda97 into main Apr 2, 2026
11 checks passed
@frankbria frankbria deleted the feature/issue-509-sessions-id-detail-page branch April 2, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frontend: /sessions/[id] detail page — wires SplitPane, AgentChatPanel, and AgentTerminal

1 participant